package com.fduss.mazegame.core.controller;

import com.fduss.mazegame.core.maze.Map;
import com.fduss.mazegame.core.maze.Maze;
import com.fduss.mazegame.core.unit.Hero;
import com.fduss.mazegame.core.unit.Monster;
import com.fduss.mazegame.core.unit.Place;
import com.fduss.mazegame.util.Direction;
import com.fduss.mazegame.util.Queue;
import com.fduss.mazegame.util.QueueLinked;

import java.util.List;
import java.util.Random;

/**
 * @author Duocai Wu
 * @Date 2017/11/13
 * @TIme 14:44
 */
public class MonsterCtrl implements Runnable {
    List<Monster> monsters;
    private Maze maze;
    private int delay;

    // maze: dependency injection
    public MonsterCtrl(Maze maze, List<Monster> monsters, int delay) {
        this.maze = maze;
        this.monsters = monsters;
        this.delay = delay;
    }

    // maze: dependency injection
    public MonsterCtrl(Maze maze, List<Monster> monsters) {
        this.maze = maze;
        this.monsters = monsters;
        this.delay = 500;
    }

    @Override
    public void run() {
        while (true) {
//            randomMoveMonsters();
            moveMonstersToChasePlayer();
            try {
                Thread.sleep(this.delay);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void moveMonstersToChasePlayer() {
        for (Monster m : monsters) {
            clearVisit();
            Node target = BFS(new Node(null, null, m.getPlace()));
            if (target == null || Math.random() < 0.5) {
                randomMoveMonster(m);
            } else {
                while (target.p.p != null)
                    target = target.p;
                maze.moveMonster(m, target.d);
            }
        }
    }

    private void clearVisit() {
        Map map = maze.getMap();
        int rowNum = map.getNumRows();
        int colNum = map.getNumCols();
        for (int i = 0; i < rowNum; i++) {
            for (int j = 0; j < colNum; j++) {
                map.getPlace(i, j).setVisit(false);
            }
        }
    }

    private void randomMoveMonsters() {
        for (Monster m : monsters) {
            randomMoveMonster(m);
        }
    }

    private void randomMoveMonster(Monster monster) {
        Direction[] directions = Direction.values();
        Random random = new Random();
        Direction dc = directions[random.nextInt(directions.length)];
        boolean ok = maze.moveMonster(monster, dc);
        while (!ok) {
            dc = directions[random.nextInt(directions.length)];
            ok = maze.moveMonster(monster, dc);
        }
    }

    private Node BFS(Node s) {
        Queue<Node> queue = new QueueLinked<>();
        queue.enqueue(s);
        while (!queue.isEmpty()) {
            Node u = queue.dequeue();
            for (Direction d : Direction.values()) {
                Place p = u.place.placeAt(d);
                if (p != null && p.isWalkable() && !p.isVisit()) {
                    p.setVisit(true);
                    Node newNode = new Node(u, d, p);
                    for (int i = 0; i < p.getEntiesNum(); i++) {
                        if (p.getEntity(i) instanceof Hero)
                            return newNode;
                    }
                    queue.enqueue(newNode);
                }
            }
        }
        return null;
    }

    private Node DFS(Node s) {
        for (Direction d : Direction.values()) {
            Place p = s.place.placeAt(d);
            if (p != null && p.isWalkable() && !p.isVisit()) {
                p.setVisit(true);
                Node newNode = new Node(s, d, p);
                for (int i = 0; i < p.getEntiesNum(); i++) {
                    if (p.getEntity(i) instanceof Hero)
                        return newNode;
                }
                Node found = DFS(newNode);
                if (found != null)
                    return found;
            }
        }
        return null;
    }

    class Node {
        Direction d;
        Place place;
        Node p;

        Node(Node p, Direction d, Place place) {
            this.p = p;
            this.d = d;
            this.place = place;
        }
    }
}
